package com.fs.uc.api.wechat.resource;

import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.teaopenapi.models.Config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fs.uc.api.open.dto.*;
import com.fs.uc.api.wechat.dto.*;
import com.fs.uc.api.wechat.service.WechatApiService;
import com.fs.uc.base.Const;
import com.fs.uc.base.MinioUtil;
import com.fs.uc.base.MyConfig;
import com.fs.uc.core.behavior.PersonBehavior;
import com.fs.uc.security.JwtUtil;
import com.fs.uc.base.domain.CheckCode;
import com.fs.uc.base.domain.Person;
import com.fs.uc.base.exception.CustomException;
import com.fs.uc.base.repo.CheckCodeRepository;
import com.fs.uc.base.repo.PersonRepository;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.nio.charset.StandardCharsets;
import java.security.spec.AlgorithmParameterSpec;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Random;
import java.util.UUID;


/**
 * 和微信小程序有关的公开接口
 *
 * @author 崔超
 * 2021/12/8
 */
@Path(Const.URL_PREFIX + "/mini")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
public class WechatResource {
    @Inject
    PersonRepository personRepo;
    @Inject
    CheckCodeRepository checkCodeRepo;
    @Inject
    @RestClient
    WechatApiService wechatApiService;
    @Inject
    PersonBehavior personBehavior;
    @Inject
    JwtUtil jwtUtil;
    @Inject
    MyConfig myConfig;
    @Inject
    MinioUtil minio;

    /*
    通过openId获取token
     */
    @GET
    @Path("/token")
    public StaffLoginInfo staffAuthState(@QueryParam("openId") @NotBlank String openId) {
        Person person = personRepo.findByOpenId(openId);
        if (person == null) {
            throw new CustomException("尚未注册，无法用openid获取token");
        }
        String token = jwtUtil.buildToken(person);
        return new StaffLoginInfo(token);
    }

    /*
    获取openID
     */
    @GET
    @Path("/openId")
    public RespGetOpenID getOpenID(@NotBlank @QueryParam("jsCode") String jsCode) throws JsonProcessingException {
        MyConfig.Wechat wechatConf = myConfig.wechat();
        String json = wechatApiService.openIdApi(wechatConf.appId(), wechatConf.appSecret(), jsCode, "authorization_code");
        ObjectMapper mapper = new ObjectMapper();
        OpenIdResult result = mapper.readValue(json, OpenIdResult.class);
        if (result.errcode != null && result.errcode != 0) {
            throw new CustomException("微信登录异常:" + result.errmsg);
        }
        return new RespGetOpenID(result.openid, result.session_key);
    }

    /*
    获取手机验证码（申请试用）
     */
    @GET
    @Path("/public/code")
    public RespCheckCode sendPublicCheckCode(@QueryParam("phone") String phone) {
        String code = buildPhoneCode(phone);
        return new RespCheckCode(code);
    }

    /*
    上传头像
     */
    @POST
    @Path("/person/avatar")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public RespUpload upload(@MultipartForm ReqUpload req) throws Exception {
        String objName = "temp/" + UUID.randomUUID() + ".jpg";
        minio.uploadImage(objName, req.file);
        String address = minio.preview(objName);
        return new RespUpload(address, objName);
    }

    /*
    是否需要注册基本信息
     */
    @GET
    @Path("/need/register")
    public boolean needRegister(@QueryParam("openId") String openId) {
        return personRepo.findByOpenId(openId) == null;
    }

    /*
    小程序注册用户
     */
    @POST
    @Path("/person/register")
    public PersonInfoWithToken userRegister(@Valid ReqPersonRegister request) throws Exception {
        String filename = minio.getFilenameFromPreviewUrl(request.photo);
        boolean exist = minio.exist(filename);
        if (!exist) {
            throw new CustomException("头像文件无法找到");
        }

        Person person = personBehavior.createByUserSelf(request.openId,request.phone, request.name);
        String newPhotoName = person.id + ".jpg";
        minio.moveFile(filename, newPhotoName);
        String photo = minio.preview(newPhotoName);

        return new PersonInfoWithToken(person.id, person.name, photo, person.phone, jwtUtil.buildToken(person));

    }

    /*
    修改用户姓名
     */
    @POST
    @Path("/person/info")
    public void updatePersonInfo(@Valid ReqUpdatePersonInfo request) {
        Person person = personRepo.getByOpenId(request.openId);
        person.name = request.name;
        personRepo.persist(person);
    }

    /*
    查询注册用户基本信息
   */
    @GET
    @Path("/person/{openId}")
    public PersonInfoWithToken getPersonInfo(@PathParam("openId") String openId) throws Exception {
        Person person = personRepo.getByOpenId(openId);
        String photoName = person.id + ".jpg";
        String address = minio.preview(photoName);
        return new PersonInfoWithToken(person.id, person.name, address, person.phone, jwtUtil.buildToken(person));
    }

    /*
     微信获取手机号
     */
    @POST
    @Path("/phone")
    public RespGetPhone getPhone(@Valid ReqGetPhone req) throws Exception {
        RespGetOpenID wechatResp = getOpenID(req.getCode());

        String str = decrypt(req.getEncryptData(), wechatResp.getSessionKey(), req.getIv());

        ObjectMapper objectMapper = new ObjectMapper();
        Map contentMap = objectMapper.readValue(str, Map.class);

        Object phone = contentMap.get("purePhoneNumber");
        if (phone == null) {
            throw new Exception("获取手机号失败");
        }
        return new RespGetPhone(phone.toString());
    }

    private String buildPhoneCode(String phone) {
        StringBuilder codeNum = new StringBuilder();
        int[] numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        Random random = new Random();
        for (int i = 0; i < 4; i++) {
            int next = random.nextInt(10000);
            codeNum.append(numbers[next % 10]);
        }

        String code = codeNum.toString();
        sendPhoneCode(phone, code);
        registerPhoneCode(phone, code);
        return code;
    }

    private void registerPhoneCode(String phone, String code) {
        CheckCode checkCode = checkCodeRepo.findByPhone(phone);
        if (checkCode == null) {
            checkCode = new CheckCode();
            checkCode.code = code;
            checkCode.phone = phone;
            checkCode.createTime = LocalDateTime.now();
            checkCodeRepo.persist(checkCode);
        } else {
            checkCode.code = code;
            checkCode.createTime = LocalDateTime.now();
            checkCodeRepo.persist(checkCode);
        }
    }

    private void sendPhoneCode(String phone, String code) {

        MyConfig.Aliyun aliyunConfig = myConfig.aliyun();
        Config config = new Config()
                .setAccessKeyId(aliyunConfig.accessKeyId())
                .setAccessKeySecret(aliyunConfig.accessKeySecret())
                .setEndpoint("dysmsapi.aliyuncs.com");
        try {
            Client client = new Client(config);
            SendSmsRequest sms = new SendSmsRequest();
            sms.setSignName(aliyunConfig.signName());
            sms.setTemplateCode(aliyunConfig.templateCode());
            sms.setPhoneNumbers(phone);
            String template = String.format("{\"code\":\"%s\"}", code);
            sms.setTemplateParam(template);
            client.sendSms(sms);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("调用阿里云短信sdk异常");
        }
    }

    private String decrypt(String data, String key, String iv) {
        //被加密的数据
        byte[] dataByte = Base64.decodeBase64(data);
        //加密秘钥
        byte[] keyByte = Base64.decodeBase64(key);
        //偏移量
        byte[] ivByte = Base64.decodeBase64(iv);
        try {
            AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivByte);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            return new String(cipher.doFinal(dataByte), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new CustomException(e.getMessage());
        }
    }
}
